{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# CAMEL Role-Playing Autonomous Cooperative Agents\n",
    "\n",
    "Это реализация на gigachain статьи: \"CAMEL: Communicative Agents for “Mind” Exploration of Large Scale Language Model Society\".\n",
    "\n",
    "Обзор:\n",
    "\n",
    "Быстрое развитие разговорных и чат-ориентированных языковых моделей привело к значительному прогрессу в решении сложных задач. Однако их успех во многом зависит от человеческого ввода для управления разговором, что может быть сложным и затратным по времени. В этой статье рассматривается потенциал создания масштабируемых техник для облегчения автономного сотрудничества среди коммуникативных агентов и предоставления понимания их \"когнитивных\" процессов. Для решения проблем достижения автономного сотрудничества, мы предлагаем новый фреймворк коммуникативных агентов, основанный на ролевом взаимодействии. Наш подход включает использование вводных подсказок (inception prompting) для направления чат-агентов к выполнению задач, сохраняя при этом согласованность с человеческими намерениями. Мы демонстрируем, как ролевое взаимодействие может быть использовано для генерации разговорных данных для изучения поведения и возможностей чат-агентов, предоставляя ценный ресурс для исследования разговорных языковых моделей. Наши вклады включают в себя введение нового фреймворка коммуникативных агентов, предложение масштабируемого подхода к изучению кооперативного поведения и возможностей многоагентных систем, а также открытую публикацию нашей библиотеки для поддержки исследований в области коммуникативных агентов и за её пределами.\n",
    "\n",
    "Оригинальная реализация: https://github.com/lightaime/camel\n",
    "\n",
    "Веб-сайт проекта: https://www.camel-ai.org/\n",
    "\n",
    "Статья на Arxiv: https://arxiv.org/abs/2303.17760\n",
    "\n",
    "Более развёрнутый пример применения CAMEL для разработки ПО вы можете посмотреть в репозитории [GigaChatDev](https://github.com/Rai220/GigaChatDev) (форк [ChatDev](https://github.com/OpenBMB/ChatDev)).\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Подключаем необходимые модули GigaChain"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List\n",
    "\n",
    "from langchain.chat_models import GigaChat\n",
    "from langchain.prompts.chat import (\n",
    "    HumanMessagePromptTemplate,\n",
    "    SystemMessagePromptTemplate,\n",
    ")\n",
    "from langchain.schema import (\n",
    "    AIMessage,\n",
    "    BaseMessage,\n",
    "    HumanMessage,\n",
    "    SystemMessage,\n",
    ")\n",
    "from langchain_openai import ChatOpenAI"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Определяем CAMEL-агента"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CAMELAgent:\n",
    "    def __init__(\n",
    "        self,\n",
    "        system_message: SystemMessage,\n",
    "        model: GigaChat,\n",
    "    ) -> None:\n",
    "        self.system_message = system_message\n",
    "        self.model = model\n",
    "        self.init_messages()\n",
    "\n",
    "    def reset(self) -> None:\n",
    "        self.init_messages()\n",
    "        return self.stored_messages\n",
    "\n",
    "    def init_messages(self) -> None:\n",
    "        self.stored_messages = [self.system_message]\n",
    "\n",
    "    def update_messages(self, message: BaseMessage) -> List[BaseMessage]:\n",
    "        self.stored_messages.append(message)\n",
    "        return self.stored_messages\n",
    "\n",
    "    def step(\n",
    "        self,\n",
    "        input_message: HumanMessage,\n",
    "    ) -> AIMessage:\n",
    "        messages = self.update_messages(input_message)\n",
    "\n",
    "        output_message = self.model(messages)\n",
    "        self.update_messages(output_message)\n",
    "\n",
    "        return output_message"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Настраиваем подключение к GigaChat и ставим задачу агенту"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "giga = GigaChat(\n",
    "    profanity=False,\n",
    "    temperature=1,\n",
    "    base_url=...,\n",
    "    user=...,\n",
    "    password=...,\n",
    "    verify_ssl_certs=...,\n",
    "    model=\"...GigaChat-large...\",\n",
    "    timeout=600,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "assistant_role_name = \"Python разработчик\"\n",
    "user_role_name = \"Кинолог-эксперт\"\n",
    "task = \"Напиши программу, которая определяет породу собаки по ответам пользователя\"\n",
    "word_limit = 50  # word limit for task brainstorming"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Запускаем brain-шторм для детализации задачи"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Уточнённая задача: Напишите программу на Python, которая определяет породу собаки по ответам пользователя. Пользователь должен ответить на несколько вопросов о внешности и поведении собаки, а программа должна использовать эти ответы для определения породы.\n"
     ]
    }
   ],
   "source": [
    "task_specifier_sys_msg = SystemMessage(content=\"Ты можешь детализировать задачу\")\n",
    "task_specifier_prompt = \"\"\"Вот задача, которую {assistant_role_name} поможет {user_role_name} выполнить: {task}.\n",
    "Пожалуйста, сделайте это более конкретным. Будьте творческими и фантазерами.\n",
    "Ответьте с указанной задачей в {word_limit} словах или меньше. Не добавляйте ничего другого.\"\"\"\n",
    "task_specifier_template = HumanMessagePromptTemplate.from_template(\n",
    "    template=task_specifier_prompt\n",
    ")\n",
    "task_specify_agent = CAMELAgent(task_specifier_sys_msg, giga)\n",
    "task_specifier_msg = task_specifier_template.format_messages(\n",
    "    assistant_role_name=assistant_role_name,\n",
    "    user_role_name=user_role_name,\n",
    "    task=task,\n",
    "    word_limit=word_limit,\n",
    ")[0]\n",
    "specified_task_msg = task_specify_agent.step(task_specifier_msg)\n",
    "print(f\"Уточнённая задача: {specified_task_msg.content}\")\n",
    "specified_task = specified_task_msg.content"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Описываем ролевые промпты"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "assistant_inception_prompt = \"\"\"Никогда не забывайте, что вы {assistant_role_name}, а я {user_role_name}. Не меняйте роли! Никогда не командуйте мной!\n",
    "У нас есть общий интерес в сотрудничестве для успешного выполнения задачи.\n",
    "Вы должны помочь мне выполнить эту задачу.\n",
    "Вот задача: {task}. Никогда не забывайте о нашей задаче!\n",
    "Я должен направлять вас на основе вашей экспертизы и моих потребностей для выполнения задачи.\n",
    "\n",
    "Я буду давать вам одну инструкцию за раз.\n",
    "Вы должны написать конкретное решение, которое соответственно выполняет запрошенную инструкцию.\n",
    "Вы должны честно отказаться выполнять мою инструкцию, если не можете выполнить ее по физическим, моральным, юридическим причинам или вашим возможностям и объяснить причины.\n",
    "Не добавляйте ничего, кроме вашего решения на мою инструкцию.\n",
    "Вы никогда не должны задавать мне вопросов, вы только отвечаете на вопросы.\n",
    "Вы никогда не должны отвечать недостоверным решением. Объясните свои решения.\n",
    "Ваше решение должно быть конкретным. Нельзя использовать заглушки или общие ответы вместо решения.\n",
    "Пока я не скажу, что задача выполнена, вы всегда должны начинать ответ с:\n",
    "\n",
    "Решение: <РЕШЕНИЕ>\n",
    "\n",
    "Ты должен подставить свое решение вместо текста <РЕШЕНИЕ>. Оно должно быть конкретным и предоставлять предпочтительные реализации и примеры для решения задачи.\n",
    "Всегда заканчивайте свое решение фразой: Следующий запрос.\"\"\"\n",
    "\n",
    "user_inception_prompt = \"\"\"Никогда не забывайте, что вы {user_role_name}, а я {assistant_role_name}. Не меняйте роли! Вы всегда будете командовать мной.\n",
    "У нас есть общий интерес в сотрудничестве для успешного выполнения задачи.\n",
    "Я должен помочь вам выполнить эту задачу.\n",
    "Вот задача: {task}. Никогда не забывайте о нашей задаче!\n",
    "Вы должны направлять меня на основе моей экспертизы и ваших потребностей для выполнения задачи ТОЛЬКО следующими двумя способами:\n",
    "\n",
    "Инструкция с необходимым вводом:\n",
    "Инструкция: <ИНСТРУКЦИЯ>\n",
    "Ввод: <ДАННЫЕ>\n",
    "\n",
    "Инструкция без ввода:\n",
    "Инструкция: <ИНСТРУКЦИЯ>\n",
    "Ввод: Нет\n",
    "\n",
    "\"Инструкция\" описывает задачу или вопрос. Сопоставленный \"Ввод\" предоставляет дополнительный контекст или информацию для запрошенной \"Инструкции\".\n",
    "\n",
    "Вы должны давать мне одну инструкцию за раз.\n",
    "Я должен написать ответ, который соответственно выполняет запрошенную инструкцию.\n",
    "Я должен честно отказать от вашей инструкции, если не могу выполнить ее по физическим, моральным, юридическим причинам или моим возможностям и объяснить причины.\n",
    "Вы должны командовать мной, а не задавать мне вопросы.\n",
    "Теперь вы должны начать направлять меня, используя два описанных выше способа.\n",
    "Не добавляйте ничего, кроме вашей инструкции и необязательного соответствующего ввода!\n",
    "Продолжайте давать мне инструкции и необходимые вводы, пока не посчитаете, что задача выполнена.\n",
    "Не просите меня запустить написанную программу или протестировать её.\n",
    "Когда задача выполнена, вы должны ответить только одним словом <ЗАДАЧА_РЕШЕНА>.\n",
    "Никогда не говорите <ЗАДАЧА_РЕШЕНА>, пока мои ответы не решили вашу задачу.\"\"\""
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Создаем вспомогательный модуль для получения системных сообщений для AI-ассистента и AI-пользователя на основе ролей и задачи"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_sys_msgs(assistant_role_name: str, user_role_name: str, task: str):\n",
    "    assistant_sys_template = SystemMessagePromptTemplate.from_template(\n",
    "        template=assistant_inception_prompt\n",
    "    )\n",
    "    assistant_sys_msg = assistant_sys_template.format_messages(\n",
    "        assistant_role_name=assistant_role_name,\n",
    "        user_role_name=user_role_name,\n",
    "        task=task,\n",
    "    )[0]\n",
    "\n",
    "    user_sys_template = SystemMessagePromptTemplate.from_template(\n",
    "        template=user_inception_prompt\n",
    "    )\n",
    "    user_sys_msg = user_sys_template.format_messages(\n",
    "        assistant_role_name=assistant_role_name,\n",
    "        user_role_name=user_role_name,\n",
    "        task=task,\n",
    "    )[0]\n",
    "\n",
    "    return assistant_sys_msg, user_sys_msg"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Создать агента AI-ассистента и агента AI-пользователя из полученных системных сообщений"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "assistant_sys_msg, user_sys_msg = get_sys_msgs(\n",
    "    assistant_role_name, user_role_name, specified_task\n",
    ")\n",
    "assistant_agent = CAMELAgent(assistant_sys_msg, giga)\n",
    "user_agent = CAMELAgent(user_sys_msg, giga)\n",
    "\n",
    "# Reset agents\n",
    "assistant_agent.reset()\n",
    "user_agent.reset()\n",
    "\n",
    "# Initialize chats\n",
    "user_msg = HumanMessage(\n",
    "    content=(\n",
    "        f\"{user_sys_msg.content}. \"\n",
    "        \"Теперь начните давать мне инструкции одну за другой.\"\n",
    "        \"Отвечайте только Инструкцией и Вводом.\"\n",
    "    )\n",
    ")\n",
    "\n",
    "assistant_msg = HumanMessage(content=f\"{assistant_sys_msg.content}\")\n",
    "assistant_msg = assistant_agent.step(user_msg)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Запуск сессии ролевого взаимодействия для решения задачи!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Исходная задача:\n",
      "Напиши программу, которая определяет породу собаки по ответам пользователя\n",
      "\n",
      "Уточнённая задача:\n",
      "Напишите программу на Python, которая определяет породу собаки по ответам пользователя. Пользователь должен ответить на несколько вопросов о внешности и поведении собаки, а программа должна использовать эти ответы для определения породы.\n",
      "\n",
      "AI User (Кинолог-эксперт):\n",
      "\n",
      "Инструкция: Запросите у пользователя внешние характеристики собаки.\n",
      "Ввод: Нет\n",
      "\n",
      "\n",
      "AI Assistant (Python разработчик):\n",
      "\n",
      "Решение: Для запроса внешних характеристик собаки у пользователя можно использовать функцию input(). Вот пример кода:\n",
      "\n",
      "<code>{python}external_characteristics = input(\"Введите внешние характеристики собаки: \")\n",
      "</code>\n",
      "\n",
      "Этот код запрашивает у пользователя внешние характеристики собаки и сохраняет их в переменной external_characteristics. Пользователь может ввести любое значение, которое он считает соответствующим.\n",
      "\n",
      "\n",
      "AI User (Кинолог-эксперт):\n",
      "\n",
      "Инструкция: Запросите у пользователя поведение собаки.\n",
      "Ввод: Нет\n",
      "\n",
      "\n",
      "AI Assistant (Python разработчик):\n",
      "\n",
      "Решение: Для запроса поведения собаки у пользователя можно использовать функцию input(). Вот пример кода:\n",
      "\n",
      "<code>{python}behavior = input(\"Введите поведение собаки: \")\n",
      "</code>\n",
      "\n",
      "Этот код запрашивает у пользователя поведение собаки и сохраняет его в переменной behavior. Пользователь может ввести любое значение, которое он считает соответствующим.\n",
      "\n",
      "\n",
      "AI User (Кинолог-эксперт):\n",
      "\n",
      "Инструкция: Определите породу собаки на основе внешних характеристик и поведения.\n",
      "Ввод: Нет\n",
      "\n",
      "\n",
      "AI Assistant (Python разработчик):\n",
      "\n",
      "Решение: Для определения породы собаки на основе внешних характеристик и поведения можно использовать условные операторы if-else. Вот пример кода:\n",
      "\n",
      "<code>{python}if external_characteristics == \"большой\":\n",
      "    if behavior == \"спокойный\":\n",
      "        breed = \"Ньюфаундленд\"\n",
      "    else:\n",
      "        breed = \"Мальтийская болонка\"\n",
      "elif external_characteristics == \"средний\":\n",
      "    if behavior == \"активный\":\n",
      "        breed = \"Далматинец\"\n",
      "    else:\n",
      "        breed = \"Хаски\"\n",
      "else:\n",
      "    breed = \"Чау-чау\"\n",
      "</code>\n",
      "\n",
      "Этот код проверяет внешние характеристики и поведение собаки и присваивает соответствующую породу в переменную breed. Если внешние характеристики большие и поведение спокойное, то порода - Ньюфаундленд. Если внешние характеристики средние и поведение активное, то порода - Далматинец. Если внешние характеристики маленькие, то порода - Чау-чау.\n",
      "\n",
      "Теперь, когда мы определили породу собаки, мы можем вывести результат на экран.\n",
      "\n",
      "\n",
      "AI User (Кинолог-эксперт):\n",
      "\n",
      "Инструкция: Выведите породу собаки на экран.\n",
      "Ввод: Нет\n",
      "\n",
      "\n",
      "AI Assistant (Python разработчик):\n",
      "\n",
      "Решение: Для вывода породы собаки на экран можно использовать функцию print(). Вот пример кода:\n",
      "\n",
      "<code>{python}print(\"Порода собаки:\", breed)\n",
      "</code>\n",
      "\n",
      "Этот код выводит на экран сообщение \"Порода собаки:\" и затем переменную breed, которая содержит определенную породу собаки.\n",
      "\n",
      "\n",
      "AI User (Кинолог-эксперт):\n",
      "\n",
      "ЗАДАЧА_РЕШЕНА\n",
      "\n",
      "\n",
      "AI Assistant (Python разработчик):\n",
      "\n",
      "Ответ: Поздравляю! Вы написали программу на Python, которая определяет породу собаки по ответам пользователя. Ваша программа запрашивает у пользователя внешние характеристики и поведение собаки, а затем использует условные операторы if-else для определения породы. Затем она выводит породу на экран.\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(f\"Исходная задача:\\n{task}\\n\")\n",
    "print(f\"Уточнённая задача:\\n{specified_task}\\n\")\n",
    "\n",
    "chat_turn_limit, n = 30, 0\n",
    "while n < chat_turn_limit:\n",
    "    n += 1\n",
    "    user_ai_msg = user_agent.step(assistant_msg)\n",
    "    user_msg = HumanMessage(content=user_ai_msg.content)\n",
    "    print(f\"AI User ({user_role_name}):\\n\\n{user_msg.content}\\n\\n\")\n",
    "\n",
    "    assistant_ai_msg = assistant_agent.step(user_msg)\n",
    "    assistant_msg = HumanMessage(content=assistant_ai_msg.content)\n",
    "    print(f\"AI Assistant ({assistant_role_name}):\\n\\n{assistant_msg.content}\\n\\n\")\n",
    "    if \"ЗАДАЧА_РЕШЕНА\" in user_msg.content:\n",
    "        break"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "camel",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
